#!/usr/bin/env python3
# Copyright(c) 2017, Intel Corporation
#
# Redistribution  and  use  in source  and  binary  forms,  with  or  without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of  source code  must retain the  above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name  of Intel Corporation  nor the names of its contributors
# may be used to  endorse or promote  products derived  from this  software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,  BUT NOT LIMITED TO,  THE
# IMPLIED WARRANTIES OF  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT  SHALL THE COPYRIGHT OWNER  OR CONTRIBUTORS BE
# LIABLE  FOR  ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR
# CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT LIMITED  TO,  PROCUREMENT  OF
# SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  DATA, OR PROFITS;  OR BUSINESS
# INTERRUPTION)  HOWEVER CAUSED  AND ON ANY THEORY  OF LIABILITY,  WHETHER IN
# CONTRACT,  STRICT LIABILITY,  OR TORT  (INCLUDING NEGLIGENCE  OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import argparse
import sys
import subprocess
import os
import re

import bist_app
import bist_common
import bist_def
import bist_dma
import bist_nlb3
import bist_nlb0

FEATURES = ['fme', 'port', 'temp', 'power', 'errors all']
PRIVATE_FEATURES = {bist_common.N3000_ID: ['phy', 'mac']}


def get_afu_id(bus):
    cmd = 'fpgainfo port -B {}'.format(hex(bus))
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
    output, _ = p.communicate()
    m = re.search('Accelerator GUID\\s+:\\s(\\S+)', output.decode())
    afu_id = (m.group(1) if m else m)
    return afu_id


def print_cards(bdf):
    sys.stderr.write("\nAvailable cards:\n")
    for fpga in sorted(bdf):
        m = "bus {0}, device {1}, function {2}, device id {3}".format(
            hex(fpga['bus']), hex(fpga['device']), hex(fpga['function']),
            hex(fpga['device_id']))
        sys.stderr.write('  ' + m + '\n')


def main(args):
    all_bdfs = bist_common.get_all_fpga_bdfs(args)
    if not all_bdfs:
        sys.exit("No FPGA devices found")

    # Filter the list based on command line arguments
    bdf = bist_common.get_bdf_from_args(all_bdfs, args)
    if len(bdf) > 1:
        sys.stderr.write("Multiple cards found. "
                         "Specify a bus, device or function.\n")
        print_cards(bdf)
        sys.exit(1)

    if not bdf:
        sys.stderr.write("Could not find specified FPGA.\n")
        print_cards(all_bdfs)
        sys.exit(1)

    # Down to a single device...
    bdf = bdf[0]
    start = "==========================================================\n\n" \
            "Beginning FPGA Built-In Self-Test\n\n"\
            "=========================================================="
    print(start)

    # Display data from FPGA info
    dev_id = bdf['device_id']
    afu_id = get_afu_id(bdf['bus'])
    ret = 0
    bist_common.dev_id = dev_id
    for feature in (FEATURES + PRIVATE_FEATURES.get(dev_id, [])):
        cmd = "fpgainfo {} -B {} -D {} -F {}".format(feature, hex(bdf['bus']),
                                                     hex(bdf['device']),
                                                     hex(bdf['function']))
        ret += subprocess.call(cmd, shell=True)
        print("\n")

    if dev_id == bist_common.N3000_ID:
        for name in ['nlb0', 'dma']:
            module = 'bist_{}'.format(name)
            mode = getattr(sys.modules.get(module),
                           '{}Mode'.format(name.capitalize()))()
            print("Running mode: {}".format(mode.name))
            ret += mode.run(None, bdf,
                            bist_common.N3000_ID, afu_id)
    else:
        gbs_paths = args.gbs_paths
        afu_ids = {}
        for path in gbs_paths:
            afu_ids.update({bist_common.get_afu_id(path): path})
        if not gbs_paths:
            # Get the afu ID of the afu in the slot (defaut persona)
            afu_ids.update({bist_common.get_afu_id(bdf=bdf): ""})

        for afu_id in afu_ids.keys():
            print(afu_id)
            if afu_id == bist_common.get_afuid(
                          bist_def.DefaultMode().mode_list, dev_id):
                mode = bist_def.DefaultMode()
            elif afu_id == bist_common.get_afuid(
                            bist_nlb3.Nlb3Mode().mode_list, dev_id):
                mode = bist_nlb3.Nlb3Mode()
            elif afu_id == bist_common.get_afuid(
                            bist_dma.DmaMode().mode_list, dev_id):
                mode = bist_dma.DmaMode()
            elif afu_id == bist_common.get_afuid(
                            bist_app.BistMode().mode_list, dev_id):
                mode = bist_app.BistMode()
            else:
                sys.exit("Unknown AFU for available ID {}\n".format(afu_id))
            print("Running mode: {}".format(mode.name))
            ret += mode.run(afu_ids.get(afu_id), bdf)

    print("\nBuilt-in Self-Test Completed.\n")
    sys.exit(ret)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    bist_common.global_arguments(parser)
    args = bist_common.parse_args(parser)
    bist_common.check_required_cmds()
    main(args)
